import click

from sqlalchemy import Table

from base.sqlalchemy import Base
from models import *


class Generate:
    @staticmethod
    def api(app: str, model: Base):
        description = (model.__doc__ or "").strip().splitlines()[0]

        lines = []

        lines.append('"""')
        lines.append(description)
        lines.append('"""')
        lines.append("")

        lines.append("from fastapi import APIRouter")
        lines.append("")
        lines.append("from base.crud import CRUD")
        lines.append("")
        lines.append(f"from app.{app}.schemas import *")
        lines.append("from models import *")
        lines.append("")

        lines.append("app = APIRouter()")
        lines.append("")
        lines.append("")

        lines.append(f"class {model.__name__}View(CRUD):")
        lines.append(f"    model = {model.__name__}")
        lines.append("    router = app")
        lines.append(f'    scope = "{app}"')
        lines.append("")
        lines.append("")

        lines.append(f"{model.__name__}View.initialize()")
        lines.append(f"{model.__name__}View.initialize_choice()")
        lines.append(f"{model.__name__}View.initialize_foreignkey()")

        return "\n".join(lines)

    @staticmethod
    def relationship(
        app: str, model: Base, relationship_model: Base, table: Table | Base
    ):
        name = None

        for x in Base.__subclasses__():
            if isinstance(table, Base):
                if x == table:
                    name = x.__name__

                    break
            else:
                if x.__table__ == table:
                    name = x.__name__

                    break

        if not name:
            for k, v in globals().items():
                if v == table:
                    name = k

                    break

        lines = []

        lines.append(
            f"class {model.__name__}{relationship_model.__name__}RelationshipView(CRUD):"
        )
        lines.append(f"    model = {model.__name__}")
        lines.append("    router = app")
        lines.append(f'    scope = "{app}"')
        lines.append("")
        lines.append("")

        lines.append(
            f"{model.__name__}{relationship_model.__name__}RelationshipView.initialize_relationship({relationship_model.__name__}, {name})"
        )

        return "\n".join(lines)

    @staticmethod
    def schema(model: Base):
        description = (model.__doc__ or "").strip().splitlines()[0]

        map = {}

        for c in model.__table__.c:
            if isinstance(c.type, Integer):
                type = "int"
            elif isinstance(c.type, Float):
                type = "float"
            elif isinstance(c.type, Boolean):
                type = "bool"
            elif isinstance(c.type, String):
                type = "str"
            elif isinstance(c.type, Date):
                type = "datetime.datetime"
            elif isinstance(c.type, DateTime):
                type = "datetime.datetime"
            elif isinstance(c.type, Time):
                type = "datetime.time"
            elif isinstance(c.type, JSON):
                type = "dict"
            elif isinstance(c.type, ARRAY):
                if isinstance(c.type.item_type, Integer | ChoiceType):
                    type = "list[int]"
                if isinstance(c.type.item_type, String):
                    type = "list[str]"
                else:
                    type = "list[dict]"
            elif isinstance(c.type, ChoiceType):
                type = "ChoiceModel"
            elif isinstance(c.type, CoordinateType):
                type = "CoordinateModel"
            else:
                continue

            if c.name.startswith("inner_"):
                continue

            if c.name.endswith("data"):
                continue

            if c.name in (
                "password",
                "create_by",
                "create_time",
                "update_by",
                "update_time",
            ):
                continue

            if c.nullable and not c.default and not c.server_default:
                nullable = True
            else:
                nullable = False

            if c.foreign_keys:
                name = c.name.replace("_id", "")
                foreignkey = True
            else:
                name = c.name
                foreignkey = False

            map[name] = (type, nullable, foreignkey)

        lines = []

        lines.append("from base.schemas import *")
        lines.append("")
        lines.append("")
        lines.append("# ----------------------------------------------------------")
        lines.append("#")
        lines.append("# " + " " * ((58 - len(description)) // 2) + description)
        lines.append("#")
        lines.append("# ----------------------------------------------------------")
        lines.append("")
        lines.append("")

        lines.append(f"class {model.__name__}PaginationModel(BaseModel):")
        lines.append('    """')
        lines.append(f"    {description}分页信息")
        lines.append('    """')
        lines.append("")

        for name, options in map.items():
            type, nullable, foreignkey = options

            if foreignkey:
                lines.append(f"    {name}: NameModel | None")
            else:
                if nullable:
                    lines.append(f"    {name}: {type} | None")
                else:
                    lines.append(f"    {name}: {type}")

        lines.append("")

        lines.append("    def update(self, obj):")

        for name, options in map.items():
            type, nullable, foreignkey = options

            if foreignkey:
                lines.append(f"        self.{name} = NameModel.from_obj(obj.{name})")

        lines.append("")
        lines.append("")

        lines.append(f"class {model.__name__}Model({model.__name__}PaginationModel):")
        lines.append('    """')
        lines.append(f"    {description}信息")
        lines.append('    """')
        lines.append("")
        lines.append("    pass")
        lines.append("")
        lines.append("")

        lines.append(f"class {model.__name__}CreateModel(BaseModel):")
        lines.append('    """')
        lines.append(f"    {description}新建信息")
        lines.append('    """')
        lines.append("")

        for name, options in map.items():
            type, nullable, foreignkey = options

            if name in ("id",):
                continue

            if foreignkey:
                if nullable:
                    lines.append(f"    {name}: int | None")
                else:
                    lines.append(f"    {name}: int")
            else:
                if nullable:
                    lines.append(f"    {name}: {type} | None")
                else:
                    lines.append(f"    {name}: {type}")

        lines.append("")
        lines.append("")

        lines.append(f"class {model.__name__}UpdateModel({model.__name__}CreateModel):")
        lines.append('    """')
        lines.append(f"    {description}更新信息")
        lines.append('    """')
        lines.append("")
        lines.append("    pass")

        return "\n".join(lines)

    @staticmethod
    def login(app: str, model_name: str):
        lines = []

        lines.append('"""')
        lines.append("登录")
        lines.append('"""')
        lines.append("")

        lines.append("from fastapi import APIRouter, Request")
        lines.append("")
        lines.append("from base.auth import make_token")
        lines.append("from base.schemas import *")
        lines.append("from config.config import SMSCODE_EXPIRED")
        lines.append("from config.database import Session")
        lines.append("")
        lines.append(f"from models import *")
        lines.append("")
        lines.append("")

        lines.append("app = APIRouter()")
        lines.append("")
        lines.append("")

        lines.append("class LoginView(Request):")
        lines.append('    @app.post("/login", response_model=LoginModel, summary="登录")')
        lines.append("    async def login(request: Request, data: LoginArgsModel):")
        lines.append('        """')
        lines.append("        登录")
        lines.append('        """')
        lines.append("")
        lines.append("        with Session() as session, session.begin():")
        lines.append("            auth_user = session.execute(")
        lines.append(
            f"                sql.select({model_name}).where({model_name}.phone == data.phone)"
        )
        lines.append("            ).scalar()")
        lines.append("")
        lines.append("            if not auth_user:")
        lines.append("                raise HTTPException(")
        lines.append('                    detail="账号错误",')
        lines.append("                    status_code=HTTP_400_BAD_REQUEST,")
        lines.append("                )")
        lines.append("")
        lines.append("            if data.password:")
        lines.append("                if auth_user.password != data.password:")
        lines.append("                    raise HTTPException(")
        lines.append('                        detail="密码错误",')
        lines.append("                        status_code=HTTP_400_BAD_REQUEST,")
        lines.append("                    )")
        lines.append("            else:")
        lines.append("                sms = session.execute(")
        lines.append(
            "                    sql.select(SmsCode).where(SmsCode.phone == auth_user.phone)"
        )
        lines.append("                ).scalar()")
        lines.append("")
        lines.append("                if not sms:")
        lines.append("                    raise HTTPException(")
        lines.append('                        detail="请先获取验证码",')
        lines.append("                        status_code=HTTP_400_BAD_REQUEST,")
        lines.append("                    )")
        lines.append("")
        lines.append("                if sms.code != data.code:")
        lines.append("                    raise HTTPException(")
        lines.append('                        detail="验证码错误",')
        lines.append("                        status_code=HTTP_400_BAD_REQUEST,")
        lines.append("                    )")
        lines.append("")
        lines.append("                if (")
        lines.append(
            "                    arrow.get(sms.update_time).shift(minutes=-SMSCODE_EXPIRED)"
        )
        lines.append("                    > utcnow()")
        lines.append("                ):")
        lines.append("                    raise HTTPException(")
        lines.append('                        detail="验证码已过期, 请重新获取",')
        lines.append("                        status_code=HTTP_400_BAD_REQUEST,")
        lines.append("                    )")
        lines.append("")
        lines.append("            if not auth_user.first_login:")
        lines.append("                auth_user.first_login = utcnow()")
        lines.append("")
        lines.append("            auth_user.last_login = utcnow()")
        lines.append("            session.add(auth_user)")
        lines.append("")
        lines.append(f'            token = make_token(auth_user.id, "{app}")')
        lines.append("")
        lines.append("            return LoginModel(token=token)")

        return "\n".join(lines)

    @staticmethod
    def upload(app: str):
        lines = []

        lines.append('"""')
        lines.append("上传文件")
        lines.append('"""')
        lines.append("")

        lines.append("import imghdr")
        lines.append("")
        lines.append("from io import BytesIO")
        lines.append("from pathlib import Path")
        lines.append("")
        lines.append("from fastapi import APIRouter, Request, UploadFile")
        lines.append("from PIL import Image")
        lines.append("")

        lines.append("from base.auth import authentication")
        lines.append("from base.schemas import *")
        lines.append("from config.config import PREFIX")
        lines.append("from utils.utils import *")
        lines.append("")
        lines.append("")

        lines.append("app = APIRouter()")
        lines.append("")
        lines.append("")

        lines.append("class UploadView(Request):")
        lines.append(
            '    @app.post("/upload", response_model=SuccessModel, summary="上传文件")'
        )
        lines.append("    async def upload(request: Request, file: UploadFile):")
        lines.append('        """')
        lines.append("        上传文件")
        lines.append('        """')
        lines.append("")
        lines.append(f'        auth_user = authentication(request.user, "{app}")')
        lines.append("")
        lines.append("        try:")
        lines.append("            contents = await file.read()")
        lines.append("        except BaseException:")
        lines.append("            raise HTTPException(")
        lines.append('                detail="读取文件失败",')
        lines.append("                status_code=HTTP_400_BAD_REQUEST,")
        lines.append("            )")
        lines.append("")
        lines.append("        query_params = request.query_params")
        lines.append("")
        lines.append('        if query_params.get("filename"):')
        lines.append(
            '            filename = query_params.get("filename").split("/media/", 1)[-1]'
        )
        lines.append("        else:")
        lines.append("            filename = (")
        lines.append(f'                Path(f"uploads/{{PREFIX}}/{app}")')
        lines.append(
            '                .joinpath(f"{get_tmpfilename()}{Path(file.filename).suffix}")'
        )
        lines.append("                .as_posix()")
        lines.append("            )")
        lines.append("")
        lines.append("        ocr_contents = None")
        lines.append("")
        lines.append("        if imghdr.what(BytesIO(contents)):")
        lines.append("            ocr_contents = contents")
        lines.append("")
        lines.append('            if boolean_string(query_params.get("mark")):')
        lines.append("                image = Image.open(BytesIO(contents))")
        lines.append("                image = add_image_mark(image)")
        lines.append("")
        lines.append("                ios = BytesIO()")
        lines.append('                image.save(ios, "webp")')
        lines.append("                ios.seek(0)")
        lines.append("                contents = ios.read()")
        lines.append("")

        lines.append("        filename = upload_to_oss(filename, contents)")
        lines.append("")
        lines.append("        if not filename:")
        lines.append("            raise HTTPException(")
        lines.append('                detail="上传文件失败",')
        lines.append("                status_code=HTTP_400_BAD_REQUEST,")
        lines.append("            )")
        lines.append("")
        lines.append("        response_model = SuccessModel()")
        lines.append('        response_model.data = {"filename": filename}')
        lines.append("")
        lines.append("        if ocr_contents:")
        lines.append('            if query_params.get("ocrname"):')
        lines.append("                from utils.aip import AIPOCR")
        lines.append("")
        lines.append("                data = None")
        lines.append("                ocr = AIPOCR()")
        lines.append("")
        lines.append('                match query_params.get("ocrname"):')
        lines.append('                    case "idcard":')
        lines.append("                        data = ocr.idcard(")
        lines.append("                            content=ocr_contents,")
        lines.append(
            '                            front=boolean_string(query_params.get("front"), True),'
        )
        lines.append("                        )")
        lines.append('                    case "bankcard":')
        lines.append(
            "                        data = ocr.bankcard(content=ocr_contents)"
        )
        lines.append('                    case "business_license":')
        lines.append(
            "                        data = ocr.business_license(content=ocr_contents, extend=True)"
        )
        lines.append('                    case "buidling_info":')
        lines.append(
            "                        data = ocr.buidling_info(content=ocr_contents)"
        )
        lines.append("                if data:")
        lines.append('                    response_model.data["ocr"] = data')
        lines.append("")
        lines.append("        return response_model")

        return "\n".join(lines)


class Command:
    @click.group()
    @staticmethod
    def cmd():
        pass

    @click.command("api")
    @click.argument("app")
    @click.argument("model_name")
    @staticmethod
    def api(app: str, model_name: str):
        model = None

        for x in Base.__subclasses__():
            if x.__name__ == model_name:
                model = x

                break

        if model:
            click.echo(Generate.api(app, model))

    @click.command("relationship")
    @click.argument("app")
    @click.argument("model_name")
    @click.argument("relationship_model_name")
    @staticmethod
    def relationship(app: str, model_name: str, relationship_model_name: str):
        model = None
        relationship_model = None

        for x in Base.__subclasses__():
            if x.__name__ == model_name:
                model = x

                continue

            if x.__name__ == relationship_model_name:
                relationship_model = x

                continue

        if model and relationship_model:
            table = None

            foreignkeys = (model.__table__.name, relationship_model.__table__.name)

            for name, _table in Base.metadata.tables.items():
                names = []

                for c in _table.c:
                    if c.foreign_keys:
                        names.append(
                            (list(c.foreign_keys)[0]).target_fullname.replace(".id", "")
                        )

                if set(names) == set(foreignkeys):
                    table = _table

                    break

            if table is not None:
                click.echo(Generate.relationship(app, model, relationship_model, table))

    @click.command("schema")
    @click.argument("model_name")
    @staticmethod
    def schema(model_name: str):
        model = None

        for x in Base.__subclasses__():
            if x.__name__ == model_name:
                model = x

                break

        if model:
            click.echo(Generate.schema(model))

    @click.command("login")
    @click.argument("app")
    @click.argument("model_name")
    @staticmethod
    def login(app: str, model_name: str):
        click.echo(Generate.login(app, model_name))

    @click.command("upload")
    @click.argument("app")
    @staticmethod
    def upload(app: str):
        click.echo(Generate.upload(app))

    cmd.add_command(api)
    cmd.add_command(relationship)
    cmd.add_command(schema)

    cmd.add_command(login)
    cmd.add_command(upload)


if __name__ == "__main__":
    Command.cmd()
